#ifndef INCLUDE_GUARD_IF2CWMPHO3R7DN41Z6JGXVK8QSBUE0LA59
#define INCLUDE_GUARD_IF2CWMPHO3R7DN41Z6JGXVK8QSBUE0LA59

/*
 * norx.hpp
 * This file is part of norxx
 *
 * Copyright (C) 2014 - Sebastian Gesemann
 *
 * norxx is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * norxx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with norxx. If not, see <http://www.gnu.org/licenses/>.
 */

#include <cassert>
#include "norx_bits.hpp"
#include "norx_perm.hpp"

namespace norx {

const unsigned nonce_words        =  2;
const unsigned key_words          =  4;
const unsigned rate_words         = 10;
const unsigned atag_words_default =  4;
const unsigned atag_words_max     = 10;

enum domain_t
{
	domain_zero      = 0,
	domain_header    = 1,
	domain_payload   = 2,
	domain_trailer   = 4,
	domain_tag       = 8,
	domain_branching = 0x10,
	domain_merging   = 0x20
};

template<int W> struct constants;

template<> struct constants<32> {
	typedef u32 type;
	static const type (&u())[10] {
		static const type consts[] = {
			0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0x254F537A,
			0x38531D48, 0x839C6E83, 0xF97A3AE5, 0x8C91D88C, 0x11EAFB59
		};
		return consts;
	}
};

template<> struct constants<64> {
	typedef u64 type;
	static const type (&u())[10] {
		static const type consts[] = {
			0x243F6A8885A308D3, 0x13198A2E03707344, 0xA4093822299F31D0,
			0x082EFA98EC4E6C89, 0xAE8858DC339325A1, 0x670A134EE52D7FA6,
			0xC4316D80CD967541, 0xD21DFBF8B630B762, 0x375A18D261E7F892,
			0x343D1F187D92285B
		};
		return consts;
	}
};

/// initialize state s with given key, nonce and parameters
/// (R = number of rounds, D = parallelism, Abits = number of bits
/// in auth tag)
/// Note: The specification says F^R should be applied twice
/// but I find it more convenient to wait a little for the second F^R
/// application since we don't yet know the domain separator 'v'.
template<int W>
void init(array_ref<u8> const s, ///< state
          array_ref<const u8> k, ///< key
          array_ref<const u8> n, ///< nonce
          int R, int D, int Abits) ///< more parameters
{
	typedef typename constants<W>::type T;
	const int opw = W/8; // octets per word
	(void)opw;
	assert(s.size() == state_words*opw);
	assert(k.size() == key_words*opw);
	assert(n.size() == nonce_words*opw);
	assert(1 <= R && R <= 63);
	assert(0 <= D && D <= 255);
	assert(0 <= Abits && Abits <= 10*W);
	consume_words<W> cw;
	array_ref<const T> u = constants<W>::u();
	T wa[state_words];
	// copy constants, nonce and key to the state (wa)...
	array_ref<T> war (wa);
	copy(cw(u,1),cw(war,1)); // copy 1 word  from u to wa
	copy(cw(n,2),cw(war,2)); // copy 2 words from n to wa
	copy(cw(u,1),cw(war,1)); // copy 1 word  from u to wa
	copy(cw(k,4),cw(war,4)); // copy 4 words from k to wa
	copy(cw(u,8),cw(war,8)); // copy 8 words from u to wa
	assert(!u);
	assert(!n);
	assert(!k);
	assert(!war);
	war = wa;
	/// add parameters to state as diversifier...
	wa[14] ^= (T(R) << 26) ^ (T(D) << 18) ^ (T(W) << 10) ^ Abits;
	F(R,war); // apply F R times in-place
	copy(war,s); // copy word array to u8 array
}

/// Common aspects between NORX32 and NORX64
struct common
{
protected:
	int R, D, Abytes;
	unsigned rate_bytes;
	unsigned byte_index;
	domain_t active_domain;

	void feed(array_ref<u8> buffer,
              domain_t what, array_ref<const u8> data);

	void encrypt(array_ref<u8> buffer,
                 array_ref<const u8> in,
                 array_ref<u8> out);

	void decrypt(array_ref<u8> buffer,
                 array_ref<const u8> in,
                 array_ref<u8> out);

	void tag_generate(array_ref<u8> buffer, array_ref<u8> out);

	bool tag_check(array_ref<u8> buffer, array_ref<const u8> in);

	/// add current domain separator and apply F^R in-place
	virtual void ds_and_F(int R) = 0;

	~common() {}

private:
	void finish(array_ref<u8> buff);
	unsigned moredata(std::size_t howmuch);
};

template<int W>
class cipher : private common
{
public:
	void reset(array_ref<const u8> key,
	           array_ref<const u8> nonce,
	           int R, int D, int Abytes);

	void feed(domain_t what, array_ref<const u8> data)
	{ common::feed(buff,what,data); }

	void encrypt(array_ref<const u8> in, array_ref<u8> out)
	{ common::encrypt(buff,in,out); }

	void decrypt(array_ref<const u8> in, array_ref<u8> out)
	{ common::decrypt(buff,in,out); }

	void tag_generate(array_ref<u8> out)
	{ common::tag_generate(buff,out); }

	bool tag_check(array_ref<const u8> in)
	{ return common::tag_check(buff,in); }

protected:
	void ds_and_F(int R)
	{
		buff[(W/8)*15] ^= unsigned(active_domain);
		norx::F<W>(R,buff,buff);
	}

private:
	u8 buff[W/8*state_words];
};

template<int W>
void cipher<W>::reset(array_ref<const u8> key,
                      array_ref<const u8> nonce,
                      int R, int D, int Abytes)
{
	assert(D==1 && "D!=1 not yet supported");
	init<W>(buff,key,nonce,R,D,Abytes*8);
	this->R = R;
	this->D = D;
	this->Abytes = Abytes;
	this->rate_bytes = W/8*rate_words;
	this->byte_index = this->rate_bytes;
	this->active_domain = domain_zero;
}

} // namespace norx

#endif

